package com.dd.keel.core.common.utils;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.NetworkInterface;
import java.net.Inet6Address;
import java.util.StringTokenizer;
import java.util.Enumeration;
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Date;
import java.lang.StringBuilder;
import java.io.IOException;
import java.math.BigInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dd.keel.core.common.exception.*;

/**
 * Network util
 * @author Kevin.XU
 *
 */
public class NetUtil {
	
	final static private Logger LOGGER = LoggerFactory.getLogger(NetUtil.class);
	
	static private Map<Integer,Date> registered_ports = Collections.synchronizedMap( new HashMap<Integer,Date>() );
	
	/**
	 * such as 218.202.197.128-130
	 * @param ipAddressExpress
	 * @return
	 */
	public static String[] parseIPAddressExpress(String ipAddressExpress)
	{
		if(ipAddressExpress==null) return null;
		if(ipAddressExpress.indexOf('-')!=-1)
		{
			ipAddressExpress = ipAddressExpress.trim();
			int pos = ipAddressExpress.lastIndexOf('.');
			String address_prefix = ipAddressExpress.substring(0,pos+1);
			String address_range = ipAddressExpress.substring(pos+1);
			int pos2 = address_range.indexOf('-');
			int start = Integer.parseInt(address_range.substring(0, pos2));
			int end = Integer.parseInt(address_range.substring(pos2+1));
			String[] result = new String[end-start+1];
			for(int i=start, index=0; i<=end; ++i, ++index)
			{
				result[index] = address_prefix + i;
			}
			return result;
		}
		else
		{
			return new String[]{ipAddressExpress.trim()};
		}
	}
	
	/**
	 * Judge valid IP4 address
	 * @param ipAddress
	 * @return
	 */
	public static boolean isValidIP4Address(String ipAddress)
	{
		String regexExpress = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}";
		if (!RegexUtil.match(regexExpress, ipAddress))
			return false;
		String[] ipAry = ipAddress.split("\\.");
		if (ipAry.length != 4)
			return false;
		for (int i = 0; i < ipAry.length; ++i) {
			try {
				int value = Integer.parseInt(ipAry[i].trim());
				if (value < 0 || value > 255) {
					return false;
				}
			} catch (Exception ex) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * transform ip address to inetaddress object
	 * @param ipAddress, such as '10.1.22.11'
	 * @return
	 */
	public static InetAddress getAddressByIP4(String ipAddress)
	{
		StringTokenizer st = new StringTokenizer(ipAddress,".");
		byte[] ip_array = new byte[4];
		int index = 0;
		while(st.hasMoreTokens())
		{
			ip_array[index] = (byte)Integer.parseInt(st.nextToken().trim());
			++index;
		}
		//ip_array = ByteUtil.reverse(ip_array);
		InetAddress inetAddress = null;
		try{
			inetAddress = InetAddress.getByAddress(ip_array);
		}catch(Exception ex){
			LOGGER.error( ex.getMessage(), ex );
		}
		return inetAddress;
	}
	
	/**
	 * For IPv4 and IPv6
	 * @param ipAddress
	 * @return
	 */
	public static InetAddress getAddress(String ipAddress)
	{
		InetAddress inetAddress = null;
		try{
			inetAddress = InetAddress.getByName(ipAddress);
		}catch(Exception ex){
			LOGGER.error( ex.getMessage(), ex );
		}
		return inetAddress;
	}
	
	public static SocketAddress getSocketAddress(String ipAddress,int port)
	{
		InetAddress inetAddress = getAddress(ipAddress);
		return new InetSocketAddress(inetAddress,port);
	}
	
	public static BigInteger getLength12NumberByIP4(String ipAddress)
	{
		if(ipAddress==null)
		{
			return null;
		}
		StringBuilder sb = new StringBuilder();
		String temp = null;
		String[] partArray = ipAddress.split("\\.");
		for(int i=0; i<partArray.length; ++i)
		{
			temp = partArray[i];
			while(temp.length()<3)
			{
				temp = "0" + temp;
			}
			sb.append(temp);
		}
		return new BigInteger(sb.toString());
	}
	
    private static int convertAndValidateNumber(String s) {
        int i = Integer.parseInt(s);
        if (i < 0) {
            throw new IllegalArgumentException("Token can not be less than 0");
        } else if (i > 255) {
            throw new IllegalArgumentException("Token can not be larger than 255");
        }
        return i;
    }
	
    public static InetSocketAddress decode(String str)
			throws UnknownHostException {
		StringTokenizer st = new StringTokenizer(str, ",");
		if (st.countTokens() != 6) {
			throw new IllegalInetAddressException("Illegal amount of tokens");
		}
		StringBuffer sb = new StringBuffer();
		try {
			sb.append(convertAndValidateNumber(st.nextToken()));
			sb.append('.');
			sb.append(convertAndValidateNumber(st.nextToken()));
			sb.append('.');
			sb.append(convertAndValidateNumber(st.nextToken()));
			sb.append('.');
			sb.append(convertAndValidateNumber(st.nextToken()));
		} catch (IllegalArgumentException e) {
			throw new IllegalInetAddressException(e.getMessage());
		}
		InetAddress dataAddr = InetAddress.getByName(sb.toString());
		// get data server port
		int dataPort = 0;
		try {
			int hi = convertAndValidateNumber(st.nextToken());
			int lo = convertAndValidateNumber(st.nextToken());
			dataPort = (hi << 8) | lo;
		} catch (IllegalArgumentException ex) {
			throw new IllegalPortException("Invalid data port: " + str);
		}

		return new InetSocketAddress(dataAddr, dataPort);
	}
    
	public static String encode(Socket socket) {
		InetAddress servAddr = socket.getInetAddress();
		int servPort = socket.getPort();
		return servAddr.getHostAddress() + ',' + servPort;
	}
    
	public static String encode(InetSocketAddress address) {
		InetAddress servAddr = address.getAddress();
		int servPort = address.getPort();
		return servAddr.getHostAddress().replace('.', ',') + ','
				+ (servPort >> 8) + ',' + (servPort & 0xFF);
	}
	
	public static boolean checkPortUnbound(int port)
	{
        ServerSocket ss = null;
        try {
            ss = new ServerSocket(port);
            ss.setReuseAddress(true);
            return true;
        } catch (IOException e) {
            // port probably in use, check next
            return false;
        } finally {
            if(ss != null) {
                try {
                    ss.close();
                } catch (IOException e) {
                    // could not close, check next
                    return false;
                }
            }
        }
	}
	
	public static String getLocalIPv6Address() throws IOException {
	    InetAddress inetAddress = null;
	    Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
	    outer: 
	    while (networkInterfaces.hasMoreElements()) {
	        Enumeration<InetAddress> inetAds = networkInterfaces.nextElement().getInetAddresses();
	        while (inetAds.hasMoreElements()) {
	            inetAddress = inetAds.nextElement();
	            //Check if it's ipv6 address and reserved address
	            if (inetAddress instanceof Inet6Address  && !isReservedAddr(inetAddress)) {
	                break outer;
	            }
	        }
	    }

	    String ipAddr = inetAddress.getHostAddress();
	    // Filter network card No
	    int index = ipAddr.indexOf('%');
	    if (index > 0) {
	        ipAddr = ipAddr.substring(0, index);
	    }

	    return ipAddr;
	}	
	
	/**
	 * Check if it's "local address" or "link local address" or
	 * "loopbackaddress"
	 * 
	 * @param ip address
	 * 
	 * @return result
	 */
	private static boolean isReservedAddr(InetAddress inetAddr) {
	    if (inetAddr.isAnyLocalAddress() || inetAddr.isLinkLocalAddress()
	        || inetAddr.isLoopbackAddress()) {
	        return true;
	    }
	    return false;
	}
	
	/**
	 * random tcp port [10000,60000]
	 * If some port have been registered, but at 5 minutes ago, you still can use it. 
	 * If return -1, means not found valid tcp port
	 * @return
	 * @throws IOException
	 */
	public static int randomTCPPort()
	{
		int tcpPort = 0;
		ServerSocket serverSocket = null;
		boolean continueTry = true;
		int tryCount = 100;
		while(continueTry && tryCount>0)
		{
			tcpPort = 10000 + RandomUtil.randomInt(50000);
			tryCount--;
			try{
				serverSocket = new ServerSocket(tcpPort);
				serverSocket.close();
				boolean canUse = false;
				if(!registered_ports.containsKey(new Integer(tcpPort)))
				{
					registered_ports.put(new Integer(tcpPort), new Date());
					canUse = true;
				}
				else
				{
					Date registerDate = registered_ports.get(new Integer(tcpPort));
					Date now = new Date();
					if(now.getTime()-registerDate.getTime() > 10 * 60 * 1000 )
					{
						registered_ports.put(new Integer(tcpPort), now);
						canUse = true;
					}
					else
					{
						canUse = false;
					}
				}
				if(canUse) {
					LOGGER.debug( "randomTCPPort :: {} is ok, return it", tcpPort );
					continueTry = false;
				}else {
					LOGGER.debug( "randomTCPPort :: {} is unok, continue try ...", tcpPort );
					continueTry = true;
				}
			}catch(Exception ex){
				LOGGER.debug( "randomTCPPort :: {} is unok (used by other), continue try ...", tcpPort );
				continueTry = true;
			}
		}
		if(continueTry) tcpPort = -1;
		return tcpPort;
	}
	
	/**
	 * parse ipv4 address
	 * @param ip
	 * @return
	 */
	public static String parseIpV4(byte[] ip)
	{
		if(ip==null || ip.length!=4)
		{
			return null;
		}
		StringBuilder sb = new StringBuilder();
		sb.append(ByteUtil.onebyte2int(ip, 0)).append('.');
		sb.append(ByteUtil.onebyte2int(ip, 1)).append('.');
		sb.append(ByteUtil.onebyte2int(ip, 2)).append('.');
		sb.append(ByteUtil.onebyte2int(ip, 3));
		return sb.toString();
	}
	
}
